package com.example.utility.crypto.sm2;

import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.*;
import java.util.Base64;

/**
 * @author 季舟
 * @Date 2024-12-27 15:41
 */
public class SM2SignatureDemo {

    /**
     * 静态初始化块，用于在类加载时添加Bouncy Castle加密提供者
     *
     * 添加Bouncy Castle作为安全提供者，以支持后续可能用到的加密、解密等安全操作
     * 这里使用静态初始化块确保类在被加载的时候就添加好提供者，避免重复添加，提高效率
     */
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static void main(String[] args) throws Exception {
        // 生成RSA密钥对
        KeyPair keyPair = generateKeyPair();

        // 将公钥转换为Base64编码的字符串形式
        String publicKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        // 将私钥转换为Base64编码的字符串形式
        String privateKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());

        // 打印公钥和私钥的Base64编码字符串
        System.out.println("公钥:"+ publicKeyBase64);
        System.out.println("私钥:"+ privateKeyBase64);

        // 定义待加密的原文数据
        final String orginalData = "12345678";
        // 打印原文数据
        System.out.println("原文:"+orginalData);

        // 使用私钥对原文数据进行签名
        byte[] sign = signByPrivateKey(orginalData, keyPair.getPrivate());
        // 将签名转换为Base64编码的字符串形式
        String signBase64 = Base64.getEncoder().encodeToString(sign);
        // 打印签名
        System.out.println("签名:"+ signBase64);

        // 使用公钥验证签名
        boolean result = verifyByPublicKey(orginalData, keyPair.getPublic(), signBase64);
        // 打印验证结果
        System.out.println("验签结果:"+result);
    }


    /**
     * 生成EC算法的密钥对
     *
     * @return KeyPair对象，包含公钥和私钥
     * @throws NoSuchAlgorithmException 如果不支持EC算法
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        // 创建EC算法的密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 返回生成的密钥对
        return keyPair;
    }

    /**
     * 使用私钥对原始数据进行签名
     *
     * @param orginalData 待签名的原始数据
     * @param privateKey 用于签名的私钥
     * @return 签名后的字节数组
     *
     * 此方法实现的是基于SM2签名算法的私钥签名过程，使用了Bouncy Castle作为安全提供者
     * SM2是一种基于椭圆曲线密码学的签名算法，SM3是其配套的散列算法，两者常用于中国国家密码标准
     *
     * 选择使用私钥进行签名主要是为了保证数据的完整性、真实性和不可否认性
     * 签名过程首先将原始数据转换为字节数组，然后使用指定的签名实例和私钥对数据进行签名
     * 签名结果是一个字节数组，可以用于验证数据的完整性和来源
     */
    public static byte[] signByPrivateKey(String orginalData, PrivateKey privateKey) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
        // 将原始数据转换为字节数组，准备进行签名
        byte[] data= orginalData.getBytes();

        // 创建签名实例，指定使用SM2签名算法和SM3散列算法，通过Bouncy Castle提供者
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);

        // 初始化签名操作，传入私钥
        sig.initSign(privateKey);

        // 更新签名实例，传入待签名的数据
        sig.update(data);

        // 执行签名操作，返回签名后的字节数组
        return sig.sign();
    }

    /**
     * 使用公钥进行数字签名验证
     *
     * @param orginalData 待验证的原始数据
     * @param publicKey 公钥，用于验证签名
     * @param signatureBase64 Base64编码的签名数据
     * @return 如果签名验证成功，则返回true；否则返回false
     * @throws NoSuchAlgorithmException 如果没有相应的算法异常
     * @throws NoSuchProviderException 如果没有相应的提供者异常
     * @throws InvalidKeyException 如果密钥无效异常
     * @throws SignatureException 如果签名异常
     */
    public static boolean verifyByPublicKey(String orginalData, PublicKey publicKey, String signatureBase64) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
        // 将原始数据转换为字节数组
        byte[] data=orginalData.getBytes();
        // 将Base64编码的签名数据解码为字节数组
        byte[] signature=Base64.getDecoder().decode(signatureBase64);
        // 获取SM2签名实例，使用SM3摘要算法
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        // 初始化验证操作，使用公钥
        sig.initVerify(publicKey);
        // 更新签名对象的数据
        sig.update(data);
        // 验证签名是否有效
        return sig.verify(signature);
    }

}
